#include <stdio.h>              // printf, perror
#include <stdlib.h>             // atoi
#include <string.h>             // memset
#include <unistd.h>             // close
#include <arpa/inet.h>          // inet_addr, htons
#include <sys/types.h>          // socket
#include <sys/socket.h>         // socket, connect
#include <netinet/in.h>         // sockaddr_in
#include <fcntl.h>
#include <sys/stat.h> 
#include <sys/mman.h>


#define ARGS_CHECK(argcVal, expected) { \
    if (argcVal != expected) { \
        fprintf(stderr, "Usage: ./client <ip> <port>\n"); \
        return -1; \
    } \
}

#define ERROR_CHECK(ret, retval, funcName) { \
    if (ret == retval) { \
        perror(funcName); \
        return -1; \
    } \
}

// 1.0
// 会出现粘包问题，因为tcp是流式协议，消息没有边界
// int recvFile(int sockfd){
//     char filename[4096] = {0};
//     recv(sockfd,filename,sizeof(filename),0);
//     int fd = open(filename,O_CREAT|O_RDWR,0666);
//     char buf[1000] = {0};
//     ssize_t sret = recv(sockfd,buf,sizeof(buf),0);
//     write(fd,buf,sret);
//     return 0;
// }

// 2.0
typedef struct train_s {
    int length; //火车头
    char data[1000]; // 火车车厢
    // char数组 不是表示字符串 可以保存任何数据
    //1000 不是火车车厢实际大小 是上限
}train_t;

// recvn用于替换掉recv，避免出现MSG_WAITALL不能用的问题
int recvn(int sockfd, void *buf, long total){
    char *p = (char *)buf;
    long cursize = 0;
    while(cursize < total){
        ssize_t sret = recv(sockfd, p+cursize, total - cursize, 0);
        cursize += sret;
        if(sret == 0){
            return 1;
        }
    }
    return 0;
}
// int recvFile(int sockfd){
//     char filename[4096] = {0};
//     train_t train;
//     // 先收火车头，以知道车厢实际长度
//     recv(sockfd,&train.length,sizeof(train.length),0);
//     recv(sockfd,train.data,train.length,0);
//     memcpy(filename,train.data,train.length);

//     int fd = open(filename,O_CREAT|O_RDWR,0666);
//     // char buf[1000] = {0};
//     recv(sockfd,&train.length,sizeof(train.length),0);
//     recv(sockfd,train.data,train.length,0);
//     // ssize_t sret = recv(sockfd,buf,sizeof(buf),0);
//     write(fd,train.data,train.length);
//     close(fd);
//     return 0;
// }

// 3.0
// int recvFile(int sockfd){
//     char filename[4096] = {0};
//     train_t train;
//     // 先收火车头，以知道车厢实际长度
//     recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);
//     recv(sockfd,train.data,train.length,MSG_WAITALL);
//     memcpy(filename,train.data,train.length);

//     int fd = open(filename,O_CREAT|O_RDWR|O_TRUNC,0666);
//     while(1){
//         recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);
//         if(train.length != 1000){
//             printf("train.length = %d\n", train.length);
//         }
//         if(train.length == 0){
//             break;
//         }
//         recv(sockfd,train.data,train.length,MSG_WAITALL);
//         write(fd,train.data,train.length);
//     }
//     close(fd);
//     return 0;
// }

// 4.0
// int recvFile(int sockfd){
//     char filename[4096] = {0};
//     train_t train;
//     // 先收火车头，以知道车厢实际长度
//     off_t filesize;
//     recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);
//     recv(sockfd,train.data,train.length,MSG_WAITALL);
//     memcpy(filename,train.data,train.length);
//     // 接受文件大小
//     recv(sockfd, &train.length, sizeof(train.length), MSG_WAITALL); // 应该是8
//     recv(sockfd, train.data, train.length, MSG_WAITALL);
//     memcpy(&filesize, train.data, train.length);
//     printf("filesize = %ld\n", filesize);

//     off_t cursize = 0;
//     off_t lastsize = 0; // 上一次printf的时候 文件的大小
//     off_t slice = filesize/10000; //最小分片
//     int fd = open(filename,O_CREAT|O_RDWR|O_TRUNC,0666);
//     while(1){
//         recv(sockfd,&train.length,sizeof(train.length),MSG_WAITALL);
//         if(train.length != 1000){
//             printf("train.length = %d\n", train.length);
//         }
//         if(train.length == 0){
//             break;
//         }
//         cursize += train.length;
//         recv(sockfd,train.data,train.length,MSG_WAITALL);
//         write(fd,train.data,train.length);
//         if(cursize - lastsize > slice){
//             printf("%5.2lf%%\r", cursize*100.0/filesize);
//             fflush(stdout);
//             lastsize = cursize;
//         }
//     }
//     printf("100.00%%\n");
//     close(fd);
//     return 0;
// }

// 4.1 用recvn替代recv
// int recvFile(int sockfd){
//     char filename[4096] = {0};
//     train_t train;
//     // 先收火车头，以知道车厢实际长度
//     off_t filesize;
//     recvn(sockfd,&train.length,sizeof(train.length));
//     recvn(sockfd,train.data,train.length);
//     memcpy(filename,train.data,train.length);
//     // 接受文件大小
//     recvn(sockfd, &train.length, sizeof(train.length)); // 应该是8
//     recvn(sockfd, train.data, train.length);
//     memcpy(&filesize, train.data, train.length);
//     printf("filesize = %ld\n", filesize);

//     off_t cursize = 0;
//     off_t lastsize = 0; // 上一次printf的时候 文件的大n
//     off_t slice = filesize/10000; //最小分片
//     int fd = open(filename,O_CREAT|O_RDWR|O_TRUNC,0666);
//     while(1){
//         recvn(sockfd,&train.length,sizeof(train.length));
//         if(train.length != 1000){
//             printf("train.length = %d\n", train.length);
//         }
//         if(train.length == 0){
//             break;
//         }
//         cursize += train.length;
//         recvn(sockfd,train.data,train.length);
//         write(fd,train.data,train.length);
//         if(cursize - lastsize > slice){
//             printf("%5.2lf%%\r", cursize*100.0/filesize);
//             fflush(stdout);
//             lastsize = cursize;
//         }
//     }
//     printf("100.00%%\n");
//     close(fd);
//     return 0;
// }

// 5.0
int recvFile(int sockfd){
    char filename[4096] = {0};
    train_t train;
    // 先收火车头，以知道车厢实际长度
    off_t filesize;
    recvn(sockfd,&train.length,sizeof(train.length));
    recvn(sockfd,train.data,train.length);
    memcpy(filename,train.data,train.length);
    // 接受文件大小
    recvn(sockfd, &train.length, sizeof(train.length)); // 应该是8
    recvn(sockfd, train.data, train.length);
    memcpy(&filesize, train.data, train.length);
    printf("filesize = %ld\n", filesize);

    int fd = open(filename,O_CREAT|O_RDWR|O_TRUNC,0666);
    ftruncate(fd,filesize);
    char *p = (char *)mmap(NULL, filesize, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    ERROR_CHECK(p,MAP_FAILED,"mmap");
    recvn(sockfd,p,filesize);
    munmap(p,filesize);
    close(fd);
    return 0;
}



int main(int argc, char *argv[]){
    // ./client 172.17.115.119 1234 
    ARGS_CHECK(argc,3);
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    struct sockaddr_in serverAddr;
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(atoi(argv[2]));
    serverAddr.sin_addr.s_addr = inet_addr(argv[1]);
    int ret = connect(sockfd,(struct sockaddr *)&serverAddr,sizeof(serverAddr));
    ERROR_CHECK(ret,-1,"connect");
    // sleep(3);
    recvFile(sockfd);
    close(sockfd);
    return 0;
}